/*
  ==============================================================================

    This file contains the basic framework code for a JUCE plugin processor.

  ==============================================================================
*/

#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
Dist_ampAudioProcessor::Dist_ampAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
    : AudioProcessor(
        BusesProperties()
#if !JucePlugin_IsMidiEffect
#if !JucePlugin_IsSynth
            .withInput("Input", juce::AudioChannelSet::stereo(), true)
#endif
            .withOutput("Output", juce::AudioChannelSet::stereo(), true)
#endif
            )
#endif
    , treeState(*this, nullptr, "PARAMETERS",
          {
              std::make_unique<juce::AudioParameterFloat>(
                  GAIN_ID, GAIN_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  PRESENCE_ID, PRESENCE_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  BASS_ID, BASS_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  MID_ID, MID_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  TREBLE_ID, TREBLE_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  MASTER_ID, MASTER_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  INPUT_ID, INPUT_NAME, 0.0f, 10.0f, 5.0f),
              std::make_unique<juce::AudioParameterFloat>(
                  OUTPUT_ID, OUTPUT_NAME, 0.0f, 10.0f, 5.0f),
          })
    , customVariables(CUSTOM_PARAM_ID)
    , spec({})
    , boostEnabled(true)
    , lastBoostEnabledState(false)
    , channel(1)
    , lastChannelValue(0)
    , gru(std::make_unique<GRU>(HIDDEN_SIZE))
{
    enableBoost(false);
    static float linW[] = LIN_W;
    LinearInitStruct_t linParams = { linW, LIN_B };
    static float gruWIr[] = GRU_W_IR, gruWIz[] = GRU_W_IZ, gruWIn[] = GRU_W_IN;
    static float gruWHr[HIDDEN_SIZE][HIDDEN_SIZE] = GRU_W_HR,
                 gruWHz[HIDDEN_SIZE][HIDDEN_SIZE] = GRU_W_HZ,
                 gruWHn[HIDDEN_SIZE][HIDDEN_SIZE] = GRU_W_HN;
    static float gruBIr[] = GRU_B_IR, gruBIz[] = GRU_B_IZ, gruBIn[] = GRU_B_IN;
    static float gruBHr[] = GRU_B_HR, gruBHz[] = GRU_B_HZ, gruBHn[] = GRU_B_HN;
    GruInitStruct_t gruParams = { gruWIr, gruWIz, gruWIn, gruWHr[0],
        gruWHz[0], gruWHn[0], gruBIr, gruBIz,
        gruBIn, gruBHr, gruBHz, gruBHn };
    gru->setParams("Linear", (void*)&linParams);
    gru->setParams("GRU", (void*)&gruParams);
}

Dist_ampAudioProcessor::~Dist_ampAudioProcessor()
{
    std::cout << "~Dist_ampAudioProcessor" << std::endl;
}

//==============================================================================
const juce::String Dist_ampAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

bool Dist_ampAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
    return true;
#else
    return false;
#endif
}

bool Dist_ampAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
    return true;
#else
    return false;
#endif
}

bool Dist_ampAudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
    return true;
#else
    return false;
#endif
}

double Dist_ampAudioProcessor::getTailLengthSeconds() const { return 0.0; }

int Dist_ampAudioProcessor::getNumPrograms()
{
    return 1; // NB: some hosts don't cope very well if you tell them there are 0
        // programs, so this should be at least 1, even if you're not really
        // implementing programs.
}

int Dist_ampAudioProcessor::getCurrentProgram() { return 0; }

void Dist_ampAudioProcessor::setCurrentProgram(int index) { }

const juce::String Dist_ampAudioProcessor::getProgramName(int index)
{
    return {};
}

void Dist_ampAudioProcessor::changeProgramName(int index,
    const juce::String& newName) { }

//==============================================================================
void Dist_ampAudioProcessor::prepareToPlay(double sampleRate,
    int samplesPerBlock)
{
    using FilterCoefs = juce::dsp::IIR::Coefficients<float>;
    spec = { sampleRate, (unsigned int)samplesPerBlock,
        (unsigned int)juce::AudioProcessor::getTotalNumInputChannels() };

    updataParams();

    // Boost inspired by Tube Screamer pedal
    auto& boost_highpass1 = boostChain.get<BOOST_PROC_ORDER::highpass>();
    boost_highpass1.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 720);
    boost_highpass1.prepare(spec);

    auto& boost_clipper = boostChain.get<BOOST_PROC_ORDER::clipper>();
    boost_clipper.functionToUse = [](float x) {
        float y;
        x *= 5.f;
        if (x < -1.f)
            y = -2.f / 3.f;
        else if (x < 1.f)
            y = x - std::pow(x, 3.f) / 3.f;
        else
            y = 2.f / 3.f;
        return y;
    };
    boost_clipper.prepare(spec);

    auto& boost_lowpass = boostChain.get<BOOST_PROC_ORDER::lowpass>();
    boost_lowpass.coefficients = FilterCoefs::makeFirstOrderLowPass(sampleRate, 5600);
    boost_lowpass.prepare(spec);

    enableBoost(boostEnabled);

    // CH1 Inspired by Soldano SLO preamp
    auto& CH1_input_highpass = chan1Chain.get<CHAN1_PROC_ORDER::CH1_input_highpass>();
    CH1_input_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    CH1_input_highpass.prepare(spec);

    auto& V1B = chan1Chain.get<CHAN1_PROC_ORDER::V1B>();
    V1B.setGainLinear(1.2f);
    V1B.prepare(spec);

    auto& V1B_highpass = chan1Chain.get<CHAN1_PROC_ORDER::V1B_highpass>();
    V1B_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 200);
    V1B_highpass.prepare(spec);

    auto& V1A = chan1Chain.get<CHAN1_PROC_ORDER::V1A>();
    V1A.prepare(spec);

    auto& V1A_highpass = chan1Chain.get<CHAN1_PROC_ORDER::V1A_highpass>();
    V1A_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    V1A_highpass.prepare(spec);

    auto& V2B = chan1Chain.get<CHAN1_PROC_ORDER::V2B>();
    V2B.setGainLinear(1.2f);
    V2B.prepare(spec);

    auto& V2B_lowpass = chan1Chain.get<CHAN1_PROC_ORDER::V2B_lowpass>();
    V2B_lowpass.coefficients = FilterCoefs::makeFirstOrderLowPass(sampleRate, 1600);
    V2B_lowpass.prepare(spec);

    auto& V2B_highpass = chan1Chain.get<CHAN1_PROC_ORDER::V2B_highpass>();
    V2B_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    V2B_highpass.prepare(spec);

    auto& V2A = chan1Chain.get<CHAN1_PROC_ORDER::V2A>();
    V2A.setGainLinear(1.2f);
    V2A.prepare(spec);

    auto& V2A_highpass = chan1Chain.get<CHAN1_PROC_ORDER::V2A_highpass>();
    V2A_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    V2A_highpass.prepare(spec);

    auto& V3B = chan1Chain.get<CHAN1_PROC_ORDER::V3B>();
    V3B.setGainLinear(1.f);
    V3B.prepare(spec);

    auto& V3B_highpass = chan1Chain.get<CHAN1_PROC_ORDER::V3B_highpass>();
    V3B_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    V3B_highpass.prepare(spec);

    auto& V3A = chan1Chain.get<CHAN1_PROC_ORDER::V3A>();
    V3A.setGainLinear(1.f);
    V3A.prepare(spec);

    auto& V3A_highpass = chan1Chain.get<CHAN1_PROC_ORDER::V3A_highpass>();
    V3A_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    V3A_highpass.prepare(spec);

    // CH2 Inspired by Peavey 6505+ head
    auto& CH2_input_highpass = chan2Chain.get<CHAN2_PROC_ORDER::CH2_input_highpass>();
    CH2_input_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 10);
    CH2_input_highpass.prepare(spec);

    auto& V1 = chan2Chain.get<CHAN2_PROC_ORDER::V1>();
    V1.setGainLinear(1.f);
    V1.prepare(spec);

    auto& V1_highpass = chan2Chain.get<CHAN2_PROC_ORDER::V1_highpass>();
    V1_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 338);
    V1_highpass.prepare(spec);

    auto& V2_highpass2 = chan2Chain.get<CHAN2_PROC_ORDER::V2_highpass2>();
    V2_highpass2.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 20);
    V2_highpass2.prepare(spec);

    auto& V2_lowpass = chan2Chain.get<CHAN2_PROC_ORDER::V2_lowpass>();
    V2_lowpass.coefficients = FilterCoefs::makeFirstOrderLowPass(sampleRate, 1600);
    V2_lowpass.prepare(spec);

    gru->reset();

    // Tone stack and post eq
    setToneStack(bassValue, midValue, trebleValue, presenceValue);

    auto& post_lowpass = postGainChain.get<POST_GAIN_PROC_ORDER::post_lowpass>();
    post_lowpass.coefficients = FilterCoefs::makeFirstOrderLowPass(sampleRate, 12000);
    post_lowpass.prepare(spec);

    auto& post_highpass = postGainChain.get<POST_GAIN_PROC_ORDER::post_highpass>();
    post_highpass.coefficients = FilterCoefs::makeFirstOrderHighPass(sampleRate, 50);
    post_highpass.prepare(spec);

    auto& cabSim = postGainChain.get<POST_GAIN_PROC_ORDER::cab>();
    cabSim.prepare(spec);
}

void Dist_ampAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool Dist_ampAudioProcessor::isBusesLayoutSupported(
    const BusesLayout& layouts) const
{
#if JucePlugin_IsMidiEffect
    juce::ignoreUnused(layouts);
    return true;
#else
    // This is the place where you check if the layout is supported.
    // In this template code we only support mono or stereo.
    // Some plugin hosts, such as certain GarageBand versions, will only
    // load plugins that support stereo bus layouts.
    if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
        return false;

        // This checks if the input layout matches the output layout
#if !JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
#endif

    return true;
#endif
}
#endif

void Dist_ampAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
    juce::MidiBuffer& midiMessages)
{
    if (juce::jmax(getTotalNumInputChannels(), getTotalNumOutputChannels()) == 0)
        return;

    juce::ScopedNoDenormals noDenormals;

    const auto totalNumInputChannels = getTotalNumInputChannels();
    const auto totalNumOutputChannels = getTotalNumOutputChannels();

    // In case we have more outputs than inputs, this code clears any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
    // This is here to avoid people getting screaming feedback
    // when they first compile a plugin, but obviously you don't need to keep
    // this code if your algorithm always overwrites all the output channels.
    /*for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
      buffer.clear(i, 0, buffer.getNumSamples());*/

    updataParams();

    const auto inputCh = buffer.getReadPointer(0);
    const auto outputCh = buffer.getWritePointer(0);
    const auto samples = buffer.getNumSamples();

    buffer.applyGain(inputGain);
    if (getActiveEditor() != nullptr) {
        inputRMS = 0;
        inputPeak = 0;
        for (int i = 0; i < samples; i++) {
            inputRMS += std::pow(inputCh[i], 2);
            inputPeak = juce::jmax(inputPeak, std::abs(inputCh[i]));
        }
        inputRMS = std::sqrt(inputRMS / samples);
    }
    auto inoutBlock = juce::dsp::AudioBlock<float>(buffer).getSubsetChannelBlock(0, (size_t)1);
    auto& context = juce::dsp::ProcessContextReplacing<float>(inoutBlock);
    boostChain.process(context);
    if (channel == 1) {
        chan1Chain.process(context);
    } else {
        chan2Chain.process(context);
        gru->process(inputCh, outputCh, samples);
    }
    postGainChain.process(context);

    // Copy the first output channel to second channel in stereo output case
    if (totalNumOutputChannels > 1) {
        auto channelBlock = getBusBuffer(buffer, false, 0);

        for (int i = 1; i < totalNumOutputChannels; i++) {
            for (int j = 0; j < samples; j++) {
                *channelBlock.getWritePointer(i, j) = *channelBlock.getWritePointer(0, j);
            }
        }
    }
    buffer.applyGain(outputGain);

    if (getActiveEditor() != nullptr) {
        outputRMS = 0;
        outputPeak = 0;
        for (int i = 0; i < samples; i++) {
            outputRMS += std::pow(outputCh[i], 2);
            outputPeak = juce::jmax(outputPeak, std::abs(outputCh[i]));
        }
        outputRMS = std::sqrt(outputRMS / samples);
    }
}

//==============================================================================
bool Dist_ampAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

juce::AudioProcessorEditor* Dist_ampAudioProcessor::createEditor()
{
    return new Dist_ampAudioProcessorEditor(*this);
}

//==============================================================================
void Dist_ampAudioProcessor::getStateInformation(juce::MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
    treeState.state.appendChild(customVariables, nullptr);
    std::unique_ptr<juce::XmlElement> xml(treeState.state.createXml());
    copyXmlToBinary(*xml, destData);
}

void Dist_ampAudioProcessor::setStateInformation(const void* data,
    int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory
    // block, whose contents will have been created by the getStateInformation()
    // call.
    std::unique_ptr<juce::XmlElement> theParams(
        getXmlFromBinary(data, sizeInBytes));
    if (theParams != nullptr) {
        if (theParams->hasTagName(treeState.state.getType())) {
            treeState.state = juce::ValueTree::fromXml(*theParams);
            customVariables = treeState.state.getChildWithName(CUSTOM_PARAM_ID);
            boostEnabled = customVariables.getProperty(BOOST_ID).toString() == "true"
                ? true
                : false;
            if (boostEnabled != lastBoostEnabledState) {
                enableBoost(boostEnabled);
                lastBoostEnabledState = boostEnabled;
            }
            channel = customVariables.getProperty(CHANNEL_ID).toString() == "2" ? 2 : 1;
            if (channel != lastChannelValue) {
                setChannel(channel);
                lastChannelValue = channel;
            }
            irBypassed = customVariables.getProperty(IR_BYPASS_ID).toString() == "true"
                ? true
                : false;
            irFileName = customVariables.getProperty(IR_ID).toString();
            if (irFileName != lastIrFileName) {
                if (irFileName != "")
                    setIrFile(juce::File(irFileName));
                lastIrFileName = irFileName;
            }
        }
    }
}

void Dist_ampAudioProcessor::updataParams()
{
    gainValue = *treeState.getRawParameterValue(GAIN_ID);
    if (gainValue != lastGainValue) {
        setGainValue(gainValue);
        lastGainValue = gainValue;
    }

    bassValue = *treeState.getRawParameterValue(BASS_ID);
    midValue = *treeState.getRawParameterValue(MID_ID);
    trebleValue = *treeState.getRawParameterValue(TREBLE_ID);
    presenceValue = *treeState.getRawParameterValue(PRESENCE_ID);
    if (bassValue != lastBassValue || midValue != lastMidValue || trebleValue != lastTrembelValue || presenceValue != lastPresenceValue) {
        setToneStack(bassValue, midValue, trebleValue, presenceValue);
        lastBassValue = bassValue;
        lastMidValue = midValue;
        lastTrembelValue = trebleValue;
        lastPresenceValue = presenceValue;
    }

    masterValue = *treeState.getRawParameterValue(MASTER_ID);
    if (masterValue != lastMasterValue) {
        auto& toneStack = postGainChain.get<POST_GAIN_PROC_ORDER::tone_stack>();
        toneStack.setMasterVolume(masterValue);
        lastMasterValue = masterValue;
    }

    if (boostEnabled != lastBoostEnabledState) {
        enableBoost(boostEnabled);
        lastBoostEnabledState = boostEnabled;
    }

    if (channel != lastChannelValue) {
        lastChannelValue = channel;
    }

    if (irBypassed != lastIrBypassState) {
        bypassIR(irBypassed);
        lastIrBypassState = irBypassed;
    }

    inputLevel = *treeState.getRawParameterValue(INPUT_ID);
    if (inputLevel != lastInputLevel) {
        setLevel(true, inputLevel);
        lastInputLevel = inputLevel;
    }

    outputLevel = *treeState.getRawParameterValue(OUTPUT_ID);
    if (outputLevel != lastOutputLevel) {
        setLevel(false, outputLevel);
        lastOutputLevel = outputLevel;
    }
}

void Dist_ampAudioProcessor::setGainValue(float value)
{
    using FilterCoef = juce::dsp::IIR::Coefficients<float>;

    auto& V1B_highpass2 = chan1Chain.get<CHAN1_PROC_ORDER::V1B_highpass2>();
    auto freq = 320.f - 10.f * value;
    V1B_highpass2.coefficients = FilterCoef::makeHighPass(spec.sampleRate, freq);
    V1B_highpass2.prepare(spec);

    auto& V2_highpass = chan2Chain.get<CHAN2_PROC_ORDER::V2_highpass>();
    auto freq_CH2 = 318.f - 15.9f * value;
    V2_highpass.coefficients = FilterCoef::makeHighPass(spec.sampleRate, freq_CH2);
    V2_highpass.prepare(spec);

    auto gain = value / 10.f * 1.2f;
    auto& V1A = chan1Chain.get<CHAN1_PROC_ORDER::V1A>();
    V1A.setGainLinear(gain);

    auto gainCH2 = value / 10.f * 2.f;
    auto& V2 = chan2Chain.get<CHAN2_PROC_ORDER::V2>();
    V2.setGainLinear(gainCH2);
}

void Dist_ampAudioProcessor::enableBoost(bool enable)
{
    boostEnabled = enable;
    bool bypass = !enable;
    boostChain.setBypassed<BOOST_PROC_ORDER::highpass>(bypass);
    boostChain.setBypassed<BOOST_PROC_ORDER::clipper>(bypass);
    boostChain.setBypassed<BOOST_PROC_ORDER::lowpass>(bypass);
}

void Dist_ampAudioProcessor::setIrFile(const juce::File& irFile)
{
    auto& cabSim = postGainChain.get<POST_GAIN_PROC_ORDER::cab>();
    cabSim.loadImpulseResponse(irFile, juce::dsp::Convolution::Stereo::no,
        juce::dsp::Convolution::Trim::no, 1024);
}

void Dist_ampAudioProcessor::bypassIR(const bool bypass)
{
    if (irBypassed == bypass)
        return;
    postGainChain.setBypassed<POST_GAIN_PROC_ORDER::cab>(bypass);
    irBypassed = bypass;
}

void Dist_ampAudioProcessor::setToneStack(float b, float m, float t, float p)
{
    auto& tone_stack = postGainChain.get<POST_GAIN_PROC_ORDER::tone_stack>();
    tone_stack.prepare(spec);
    tone_stack.setParams(b, m, t, p);
}

float Dist_ampAudioProcessor::getRMSValue(bool isInput)
{
    if (isInput)
        return juce::Decibels::gainToDecibels(inputRMS);
    else
        return juce::Decibels::gainToDecibels(outputRMS);
}

float Dist_ampAudioProcessor::getPeakValue(bool isInput)
{
    if (isInput)
        return inputPeak;
    else
        return outputPeak;
}

void Dist_ampAudioProcessor::setLevel(bool isInput, float value)
{
    if (isInput)
        inputGain = juce::Decibels::decibelsToGain(value * 4.f - 20.f);
    else
        outputGain = juce::Decibels::decibelsToGain(value * 4.f - 20.f);
}

void Dist_ampAudioProcessor::setChannel(int ch)
{
    channel = ch;
}

//==============================================================================
// This creates new instances of the plugin..
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new Dist_ampAudioProcessor();
}
